﻿using Cuofrisp.Console.Tools;
using Cuofrisp.Core;
using MathNet.Symbolics;

namespace Cuofrisp.Console
{
    public sealed class ResultWriter
    {
        private readonly CuofrispResult result;
        private int Size => this.result.VariableCount;
        public ResultWriter(CuofrispResult result)
        {
            this.result = result;
        }

        public void WriteTo(Stream stream)
        {
            using var writer = new StreamWriter(stream, leaveOpen: true);

            writer.WriteLine("变量：");
            this.WriteVariables(this.result.Variables, writer);
            writer.WriteLine();

            writer.WriteLine("度规：");
            this.Write(this.result.Metric, (i, j) => {
                var x = this.result.Variables[i].ToLaTeX();
                var y = this.result.Variables[j].ToLaTeX();
                return $"g_{{{{{x}}}{{{y}}}}}";
            }, writer);
            writer.WriteLine();

            writer.WriteLine("度规求逆：");
            this.Write(this.result.MetricInverse, (i, j) => {
                var x = this.result.Variables[i].ToLaTeX();
                var y = this.result.Variables[j].ToLaTeX();
                return $"g^{{{{{x}}}{{{y}}}}}";
            }, writer);
            writer.WriteLine();

            writer.WriteLine("度规求导：");
            this.Write(this.result.MetricDerivative, (i, j, k) => {
                var x = this.result.Variables[i].ToLaTeX();
                var y = this.result.Variables[j].ToLaTeX();
                var z = this.result.Variables[k].ToLaTeX();
                return $"g_{{{{{x}}}{{{y}}},{{{z}}}}}";
            }, writer);
            writer.WriteLine();

            writer.WriteLine("克氏符：");
            this.Write(this.result.Christoffel, (i, j, k) => {
                var x = this.result.Variables[i].ToLaTeX();
                var y = this.result.Variables[j].ToLaTeX();
                var z = this.result.Variables[k].ToLaTeX();
                return $@"\Gamma^{{{x}}}_{{{{{y}}}{{{z}}}}}";
            }, writer);
            writer.WriteLine();

            writer.WriteLine("克氏符求导：");
            this.Write(this.result.ChristoffelDerivative, (i, j, k, l) => {
                var x = this.result.Variables[i].ToLaTeX();
                var y = this.result.Variables[j].ToLaTeX();
                var z = this.result.Variables[k].ToLaTeX();
                var a = this.result.Variables[l].ToLaTeX();
                return $@"\Gamma^{{{x}}}_{{{{{y}}}{{{z}}},{{{a}}}}}";
            }, writer);
            writer.WriteLine();

            writer.WriteLine("曲率：");
            this.Write(this.result.Curvature, (i, j, k, l) => {
                var x = this.result.Variables[i].ToLaTeX();
                var y = this.result.Variables[j].ToLaTeX();
                var z = this.result.Variables[k].ToLaTeX();
                var a = this.result.Variables[l].ToLaTeX();
                return $@"R^{{{x}}}_{{{{{y}}}{{{z}}}{{{a}}}}}";
            }, writer);
            writer.WriteLine();
        }

        private void WriteVariables(
            SymbolicExpression[] expressions, StreamWriter stream)
        {
            stream.WriteLine("$$");
            stream.WriteLine(@"\begin{matrix}");

            stream.Write(expressions[0].ToLaTeX());
            for (int j = 1; j < this.Size; j++)
            {
                stream.Write(" & ");
                stream.Write(expressions[j].ToLaTeX());
            }

            stream.WriteLine();
            stream.WriteLine(@"\end{matrix}");
            stream.WriteLine("$$");
        }

        private void Write(
            SymbolicExpression[,] expressions,
            Func<int, int, string> symbolFactory,
            StreamWriter stream)
        {
            stream.WriteLine("$$");
            stream.WriteLine(@"\begin{matrix}");

            void WriteItem(int i, int j)
            {
                stream.Write(symbolFactory(i, j));
                stream.Write("=");
                stream.Write(expressions[i, j].ToLaTeX());
            }

            void WriteRow(int i)
            {
                WriteItem(i, 0);
                for (int j = 1; j < this.Size; j++)
                {
                    stream.Write(" & ");
                    WriteItem(i, j);
                }
            }

            WriteRow(0);
            for (int i = 1; i < this.Size; i++)
            {
                stream.WriteLine(@"\\");
                WriteRow(i);
            }

            stream.WriteLine();
            stream.WriteLine(@"\end{matrix}");
            stream.WriteLine("$$");
        }

        private void Write(
            SymbolicExpression[,,] expressions,
            Func<int, int, int, string> symbolFactory,
            StreamWriter stream)
        {
            stream.WriteLine("$$");
            stream.WriteLine(@"\begin{matrix}");

            void WriteItem(int i, int j, int k)
            {
                stream.Write(symbolFactory(i, j, k));
                stream.Write("=");
                stream.Write(expressions[i, j, k].ToLaTeX());
            }

            void WriteRow(int i, int j)
            {
                WriteItem(i, j, 0);
                for (int k = 1; k < this.Size; k++)
                {
                    stream.Write(" & ");
                    WriteItem(i, j, k);
                }
            }

            WriteRow(0, 0);
            foreach (var (i, j) in Ranges.Range2d(this.Size).Skip(1))
            {
                stream.WriteLine(@"\\");
                WriteRow(i, j);
            }

            stream.WriteLine();
            stream.WriteLine(@"\end{matrix}");
            stream.WriteLine("$$");
        }

        private void Write(
            SymbolicExpression[,,,] expressions,
            Func<int, int, int, int, string> symbolFactory,
            StreamWriter stream)
        {
            stream.WriteLine("$$");
            stream.WriteLine(@"\begin{matrix}");

            void WriteItem(int i, int j, int k, int l)
            {
                stream.Write(symbolFactory(i, j, k, l));
                stream.Write("=");
                stream.Write(expressions[i, j, k, l].ToLaTeX());
            }

            void WriteRow(int i, int j, int k)
            {
                WriteItem(i, j, k, 0);
                for (int l = 1; l < this.Size; l++)
                {
                    stream.Write(" & ");
                    WriteItem(i, j, k, l);
                }
            }

            WriteRow(0, 0, 0);
            foreach (var (i, j, k) in Ranges.Range3d(this.Size).Skip(1))
            {
                stream.WriteLine(@"\\");
                WriteRow(i, j, k);
            }

            stream.WriteLine();
            stream.WriteLine(@"\end{matrix}");
            stream.WriteLine("$$");
        }
    }
}
